package com.sunyard.utils.macutil;

import lombok.extern.slf4j.Slf4j;

import java.security.GeneralSecurityException;

/**
 * 关键字：Java MAC计算
 * <p>
 * 为了检查通讯报文是否被篡改，常需要在报文中加上一个MAC（Message Authentication Code，报文校验码）。
 * <p>
 * 在 JDK 1.4里，已包含一个 Mac 类（javax.crypto.Mac），可以生成MAC。 但它是参照HMAC（Hash-based Message Authentication Code，基于散列的消息验证代码）实现的。 有时，需要采用ANSI-X9.9算法计算MAC。
 * <p>
 * 1. 算法描述 参与ANSI X9.9 MAC计算的数据主要由三部分产生：初始数据、原始数据、补位数据。
 * 1) 算法定义：采用DEC CBC（zeroICV）或ECB算法
 * 2) 初始数据：0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00
 * 3) 原始数据:
 * 4) * 补位数据：若原始数据不是8的倍数,则右补齐0x00；若原始数据位8的整数倍，则不用补齐0x00。
 * 5) 密钥: MAC密钥
 * <p>
 * MAC的产生由以下方式完成：(最后一组数据长度若不足8的倍数，则右补齐0x00；若数据长度为8的整数倍，则无需补充0x00)
 * 初始数据 BLOCK #1 BLOCK #2 BLOCK #3 ... BLOCK #N | | | | | +-----> XOR +---> XOR +---> XOR +---> XOR | | | | | | | DES
 * ---+ DES ---+ DES ---+ DES ---> MAC | | | | KEY KEY KEY KEY
 * <p>
 * 返回 -- 加密后的缓冲区
 */

/**
 /* @ClassName: ANSIMacUtils
 * @PackageName: com.sunyard.util.macutil
 * @Description: mac计算
 * @Company: 杭州信雅达科技有限公司
 * @Author: zhix.huang
 * @Date: 2019/6/26 14:30
 * @UpdateRemark: 修改日期 修改人员 修改说明
 * @Version: V1.0
 */
@Slf4j
public class ANSIMacUtils {

    /**
     * ANSI X9.9MAC算法  <br/>
     * (1) ANSI X9.9MAC算法只使用单倍长密钥。  <br/>
     * (2)  MAC数据先按8字节分组，表示为D0～Dn，如果Dn不足8字节时，尾部以字节00补齐。 <br/>
     * (3) 用MAC密钥加密D0，加密结果与D1异或作为下一次的输入。 <br/>
     * (4) 将上一步的加密结果与下一分组异或，然后再用MAC密钥加密。<br/>
     * (5) 直至所有分组结束，取最后结果的左半部作为MAC。<br/>
     * 采用x9.9算法计算MAC (Count MAC by ANSI-x9.9).
     *
     * @param key  8字节密钥数据
     * @param data 待计算的缓冲区
     * @throws GeneralSecurityException
     */
    public static byte[] calculateANSIX9_9MAC(byte[] key, byte[] data) throws GeneralSecurityException {
        String datastr = Utils.bcd2Str(data);
        String keystr = Utils.bcd2Str(key);
        final int dataLength = data.length;
        final int lastLength = dataLength % 8;
        final int lastBlockLength = lastLength == 0 ? 8 : lastLength;
        final int blockCount = dataLength / 8 + (lastLength > 0 ? 1 : 0);

        // 拆分数据（8字节块/Block）
        byte[][] dataBlock = new byte[blockCount][8];
        for (int i = 0; i < blockCount; i++) {
            int copyLength = i == blockCount - 1 ? lastBlockLength : 8;
            System.arraycopy(data, i * 8, dataBlock[i], 0, copyLength);
            String dataBlockHex1 = Utils.bcd2Str(dataBlock[i]);
            System.out.println("第" + i + "块" + dataBlockHex1);
        }
        byte[] desXor = new byte[8];
        for (int i = 0; i < blockCount; i++) {
            byte[] tXor = DesUtils.xOr(desXor, dataBlock[i]);
            String tXorstr = Utils.bcd2Str(tXor);
            System.out.println("第" + i + "次异或：" + Utils.bcd2Str(desXor) + "和" + Utils.bcd2Str(dataBlock[i]) + "得到" + tXorstr);
            desXor = DesUtils.encryptByDesEcb(tXor, key); // DES加密
            String desXorStr = Utils.bcd2Str(desXor);
            System.out.println(tXorstr + "加密得到：" + desXorStr);
        }
        return desXor;
    }

    /**
     * 采用ANSI x9.19算法计算MAC (Count MAC by ANSI-x9.19).<br/>
     * 将ANSI X9.9的结果做如下计算<br/>
     * (6) 用MAC密钥右半部解密(5)的结果。 <br/>
     * (7) 用MAC密钥左半部加密(6)的结果。<br/>
     * (8) 取(7)的结果的左半部作为MAC。<br/>
     * @param key  16字节密钥数据
     * @param data 待计算的缓冲区
     * @throws GeneralSecurityException
     */
    public static byte[] calculateANSIX9_19MAC(byte[] key, byte[] data) throws GeneralSecurityException {
        if (key == null || data == null)
            return null;

        if (key.length != 16) {
            throw new RuntimeException("秘钥长度错误.");
        }

        byte[] keyLeft = new byte[8];
        byte[] keyRight = new byte[8];
        System.arraycopy(key, 0, keyLeft, 0, 8);
        System.arraycopy(key, 8, keyRight, 0, 8);

        byte[] result99 = calculateANSIX9_9MAC(keyLeft, data);

        byte[] resultTemp = DesUtils.decryptByDesEcb(result99, keyRight);
        return DesUtils.encryptByDesEcb(resultTemp, keyLeft);
    }

    public static void main(String[] args) throws GeneralSecurityException {
        String key = "2019072911300600";
        String context = "body={\"nextRollTime\":\"0\",\"resultCode\":\"00\",\"resultDesc\":\"成功\",\"taskList\":[{\"index\":\"1\",\"reboot\":\"1\",\"taskId\":\"20190726114211TK62dc29bbab9e40ed\",\"taskType\":\"4\"},{\"index\":\"1\",\"reboot\":\"1\",\"taskId\":\"20190726114304TKc66cb8e46eba4d86\",\"taskType\":\"5\"},{\"index\":\"1\",\"reboot\":\"1\",\"taskId\":\"20190726114317TKfbcbe89df27948aa\",\"taskType\":\"6\"},{\"index\":\"1\",\"reboot\":\"1\",\"taskId\":\"20190726114336TK5e5d030595b7409c\",\"taskType\":\"7\"}],\"token\":\"OHCocC14EXjP823SpHzAFA==\"}&head={\"apiID\":\"0001\",\"apiVersion\":\"1.00\",\"channelID\":\"3DES\",\"cryptAlg\":\"3DES\",\"msgType\":\"p\",\"orgID\":\"1\",\"sN\":\"00002106849190400020\",\"timeStamp\":\"20190729113006\",\"token\":\"OHCocC14EXjP823SpHzAFA==\"}";
        //ANSIX9_19  、ANSIX9_9
        log.info("1:" + context);
        // context = UnicodeChange.gb2312ToUtf8(context);
        log.info("2:" + context);
        byte[] b = ANSIMacUtils.calculateANSIX9_9MAC(Util.hexStringToBytes(key), context.getBytes());
        String str = Util.getHexString(b, true);
        System.out.println(str);
    }
}